package com.google.code.wicket.auth.component.marker;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;

import org.apache.wicket.Component;
import org.apache.wicket.MetaDataKey;

/**
 * Base implementation of {@link Marker} that deals with storing and retrieving
 * meta data and just lets you handle missing privileges.
 * 
 * @param <T>
 *            the privilege type
 */
public abstract class MarkerSupport<T> implements Marker<T> {

    private final Class<T> privilegeType;

    private final MetaDataKey<T[]> componentMarkerIdentifier = new MetaDataKey<T[]>() {
        private static final long serialVersionUID = 1L;
    };

    /**
     * Constructor with a type parameter (needed to create arrays).
     * 
     * @param privilegeType
     */
    public MarkerSupport(final Class<T> privilegeType) {
        this.privilegeType = privilegeType;
    }

    /**
     * Get the privilege type.
     * 
     * @return the current property value
     */
    public Class<T> getPrivilegeType() {
        return this.privilegeType;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final boolean handleComponent(final Component component) {
        final Collection<T> readValues = this.readValues(component);

        return !readValues.isEmpty()
                && this.doHandleMissingValues(component, readValues);
    }

    /**
     * Default implementation will just mark the component so it can be found by
     * {@link #handleComponent(Component)}. If you override this, don't forget
     * to call super.mark(..).
     */
    @Override
    public void mark(final Component component,
            final Collection<T> missingValues) {
        this.writeValues(component, missingValues);
    }

    /**
     * Do nothing in this implementation.
     */
    @Override
    public void markType(final Class<? extends Component> componentClass,
            final Collection<T> missingValues) {

    }

    @SuppressWarnings("unchecked")
    private void writeValues(final Component component,
            final Collection<T> missingValues) {
        if (!missingValues.isEmpty()) {
            component.setMetaData(this.componentMarkerIdentifier, missingValues
                    .toArray((T[]) Array.newInstance(this.getPrivilegeType(),
                            missingValues.size())));
        }
    }

    /**
     * Handle missing privileges.
     * 
     * @param component
     *            will not be null
     * @param missingValues
     *            will not be null or empty
     * @return true if successfully handled
     */
    protected abstract boolean doHandleMissingValues(Component component,
            Collection<T> missingValues);

    /**
     * Access missing values. If this returns a non-empty collection,
     * {@link #doHandleMissingValues(Component, Collection)} will be called.
     * 
     * @param component
     * @return
     */
    protected Collection<T> readValues(final Component component) {
        Collection<T> result;
        final T[] metaData = component
                .getMetaData(this.componentMarkerIdentifier);
        if (metaData == null || metaData.length == 0) {
            result = Collections.emptySet();
        } else {
            result = new HashSet<T>(Arrays.asList(metaData));
        }
        return result;
    }

}
